const Path = require("path");
const hash = require("ada-util/src/md5");
const AdaBundler = require("./bundler/ada");
const Maker = require("./packer/maker");
const { File, SyncFile, clone } = require("ada-util");
const Locker = require('./locker');
const fs = require('fs');
const gitLib = require('isomorphic-git');
const http = require('isomorphic-git/http/node');

class Pager {
    constructor(config) {
        this._config = config;
        this._adaBunlder = new AdaBundler(config, new Maker(config));
        this._locker = new Locker(config.distPath);
        this._versionCache = null;
        this._version = null;
    }

    get config() {
        return this._config;
    }

    get hooker() {
        return this.config.hooker;
    }

    get locker() {
        return this._locker;
    }

    getAdaVersion() {
        if (this._version) {
            return Promise.resolve(this._version);
        } else {
            if (!this._versionCache) {
                let path = Path.resolve(this.config.projectPath, './node_modules/adajs');
                this._versionCache = gitLib.getConfig({
                    fs,
                    dir: path,
                    path: 'remote.origin.url'
                }).then(git => {
                    return gitLib.fetch({
                        dir: path,
                        url: git,
                        ref: 'master',
                        fs,
                        http,
                        depth: 1,
                        singleBranch: true,
                        onAuth: () => {
                            return {
                                username: this.config.gitUser,
                                password: this.config.gitPass
                            }
                        }
                    }).then(result => {
                        return result.fetchHead;
                    }).then(version => {
                        this._version = version;
                        return version;
                    });
                }).catch(() => {
                    return require(Path.resolve(this.config.projectPath, './node_modules/adajs/package.json')).version;
                });
            }
            return this._versionCache;
        }
    }

    checkPackAda() {
        return new Promise(resolve => {
            let adaInfo = this.locker.get('ada');
            let result = true;
            this.getAdaVersion().then(version => {
                if (adaInfo) {
                    result = adaInfo.version !== version;
                }
                if (!result && adaInfo.name) {
                    result = !new File(Path.resolve(this.config.distPath, `./${adaInfo.name}`)).exist;
                    if (!result) {
                        result = this.config.develop != adaInfo.develop;
                    }
                }
                resolve(result);
            });
        });
    }

    outputAda() {
        let config = this.config;
        return this.checkPackAda().then(r => {
            if (r || process.env.pack_hard) {
                return this.hooker.emit("before-pack-ada-core").then(() => {
                    if (config.develop) {
                        return this._adaBunlder.getBundleCode(Path.resolve(config.nmodulePath, "./adajs/develop.js")).then(() => {
                            let path = Path.resolve(config.distPath, "./ada.js");
                            return this.getAdaVersion().then(version => {
                                return this.hooker.emit("after-pack-ada-core", { bundler: this._adaBunlder }).then(() => {
                                    this.locker.set('ada', { version, name: `ada.js`, develop: config.develop });
                                    return new File(path).write(this._adaBunlder.getContent());
                                });
                            });
                        });
                    } else {
                        return this._adaBunlder.getBundleCode(Path.resolve(config.nmodulePath, "./adajs/index.js")).then(code => {
                            let h = hash.md5(code).substring(0, 8);
                            let path = Path.resolve(config.distPath, `./ada.${h}.js`);
                            return this.getAdaVersion().then(version => {
                                return this.hooker.emit("after-pack-ada-core", { bundler: this._adaBunlder }).then(() => {
                                    this.locker.set('ada', { version, name: `ada.${h}.js`, develop: config.develop });
                                    return new File(path).write(this._adaBunlder.getContent());
                                });
                            });
                        });
                    }
                }).then(() => {
                    this.locker.save();
                });
            }
        });
    }

    outputIndex() {
        let config = this.config;
        let baseInfo = config.siteInfo || {};
        let adaURL = `${config.siteURL}${this.locker.get('ada').name}`;
        return (baseInfo.icons || []).reduce((a, icon) => {
            return a.then(() => {
                let path = Path.resolve(config.projectPath, icon.path);
                icon.hash = new SyncFile(path).hash();
                icon.fileName = config.develop ? Path.basename(icon.path) : `${icon.hash.substring(0, 8)}${Path.extname(icon.path)}`;
                icon.src = `${config.siteURL}logos/${icon.fileName}`;
                return new File(path).copyTo(Path.resolve(config.distPath, './logos/', icon.fileName));
            });
        }, Promise.resolve()).then(() => {
            let metaContent = (baseInfo.meta || []).map(item => {
                let props = Reflect.ownKeys(item).map(key => `${key}="${item[key]}"`).join(" ");
                return `<meta ${props}>`;
            }).join("");
            let iconsContent = (baseInfo.icons || []).map(info => {
                return `<link rel="apple-touch-icon-precomposed" sizes="${info.sizes}" href="${info.src}">`;
            }).join("");
            if (baseInfo.icons && baseInfo.icons.length > 0) {
                iconsContent += `<link rel="shortcut icon" href="${baseInfo.icons[0].src}">`;
            }
            let linkContent = (baseInfo.link || []).map(path => {
                let props = Reflect.ownKeys(path).map(key => `${key}="${path[key]}"`).join(" ");
                return `<link ${props}>`;
            }).join("");
            let styleContent = (baseInfo.style || []).map(path => {
                if (util.isObject(path)) {
                    path.rel = "stylesheet";
                    let props = Reflect.ownKeys(path).map(key => `${key}="${path[key]}"`).join(" ");
                    return `<link ${props}>`;
                } else {
                    return `<link rel="stylesheet" href="${path}">`;
                }
            }).join("");
            let scriptContent = (baseInfo.script || []).map(path => {
                if (util.isObject(path)) {
                    let props = Reflect.ownKeys(path).map(key => `${key}="${path[key]}"`).join(" ");
                    return `<script ${props}></script>`;
                } else {
                    return `<script src="${path}"></script>`;
                }
            }).join("");
            let bootMap = {
                basePath: config.siteURL,
                develop: config.develop,
                map: config.apps.map(a => {
                    return a.getPackOutputInfo();
                }),
                root: 'root'
            };
            let manifest = {};
            Reflect.ownKeys(baseInfo).forEach(key => {
                if (["keywords", "meta", "link", "style", "script"].indexOf(key) === -1) {
                    manifest[key] = clone(baseInfo[key]);
                }
            });
            let hookInfo = {
                map: bootMap,
                manifest
            };
            let ps = Promise.resolve();
            return ps.then(() => {
                let content = `<!DOCTYPE html><html><head><link rel="manifest" href="manifest.json"><meta charset="${baseInfo.charset}"><title>${baseInfo.title}</title>${metaContent}${iconsContent}${styleContent}${linkContent}${scriptContent}<script src="${adaURL}"></script><script>Ada.boot(${JSON.stringify(hookInfo.map)});</script></head><body></body></html>`;
                if (hookInfo.manifest.icons) {
                    hookInfo.manifest.icons.map(icon => {
                        return {
                            src: icon.src,
                            sizes: icon.sizes,
                            type: icon.type
                        };
                    });
                }
                return Promise.all([
                    new File(Path.resolve(config.distPath, "./manifest.json")).write(JSON.stringify(hookInfo.manifest)),
                    new File(Path.resolve(config.distPath, "./index.html")).write(content)
                ]);
            });
        });
    }
}

module.exports = Pager;